package com.alibaba.dubbo.validate;

import com.alibaba.dubbo.common.bytecode.Wrapper;
import com.alibaba.dubbo.common.utils.CollectionUtils;
import com.alibaba.dubbo.common.utils.StringUtils;
import com.alibaba.dubbo.rpc.*;
import com.alibaba.dubbo.rpc.proxy.AbstractProxyInvoker;
import com.alibaba.dubbo.rpc.proxy.javassist.JavassistProxyFactory;

import java.lang.reflect.Field;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

/**
 * 通过生成Class的字节码Wrap，来调用类中的get方法检查类的属性。
 *
 * @author zhangjuntao
 * @create 2017-10-27 下午3:22
 **/
public class ValidateUtil {

    private static final ProxyFactory PROXY_FACTORY = new JavassistProxyFactory();

    private static final Map<Class, Wrapper> CLASS_WRAPPER_MAP = new ConcurrentHashMap<>();

    private static final List<Class> VALIADATE_CLASS = Arrays.asList(NotNull.class, NotEmpty.class);


    public static void main(String[] args) {


        Arrays.stream(Person.class.getDeclaredFields())
                .filter(field -> field.isAnnotationPresent(BeanValidate.class))
                .forEach(field -> System.out.println(field.getName()));

        /*Person person = new Person();

        person.setAddr("xi'an");
        person.setAge(20);


        Person person2 = new Person();
        person2.setAddr("guang'an");
        person2.setAge(30);
        CheckBean2(person);
        CheckBean2(person2);*/


    }

    /**
     * 检查实例属性非空
     * <p>
     * PS:  com.alibaba.dubbo.common.bytecode.Wrapper#WRAPPER_MAP
     * 每次类Class和Wrap都会生成一个缓存映射。class 和 wrapper 是一一对应关系。
     *
     * @param person
     */
    public static void CheckBean(Person person) {

        // TODO: 2017/10/27 生成多个class Wrapper
        Invoker<Person> invoker = PROXY_FACTORY.getInvoker(person, Person.class, null);
        CheckBeanField(invoker, person);
    }


    /**
     * 自己写了一个Bean的属性检查。
     * <p>
     * PS: 自己缓存Wrap 。其实Wrap生成时候已将对Class和Wrap建立了一一映射关系了。
     *
     * @param o
     * @param <T>
     */
    public static <T> void CheckBean2(T o) {
        System.out.println(o.getClass() + ":" + CLASS_WRAPPER_MAP.containsKey(o.getClass()));
        CLASS_WRAPPER_MAP.putIfAbsent(o.getClass(), Wrapper.getWrapper(o.getClass()));
        final Wrapper wrapper = CLASS_WRAPPER_MAP.get(o.getClass());
        final AbstractProxyInvoker<T> abstractProxyInvoker = new AbstractProxyInvoker<T>(o, (Class<T>) o.getClass(), null) {
            @Override
            protected Object doInvoke(T proxy, String methodName,
                                      Class<?>[] parameterTypes,
                                      Object[] arguments) throws Throwable {
                return wrapper.invokeMethod(proxy, methodName, parameterTypes, arguments);
            }
        };

        CheckBeanField(abstractProxyInvoker, o);

    }


    /**
     * 检查类中属性是否完整
     *
     * @param invoker
     * @param o
     * @param <T>
     */
    public static <T> void CheckBeanField(Invoker<T> invoker, T o) {
        Field[] declaredFields = o.getClass().getDeclaredFields();

        Arrays.stream(declaredFields)
                .forEach(field -> {

                    //1、Result result = invoker.invoke(new RpcInvocation(method, null, null));

                    List<Class> collect = (List<Class>) VALIADATE_CLASS.stream()
                            .filter(annotation -> field.getAnnotation(annotation) != null)
                            .collect(Collectors.toList());

                    if (CollectionUtils.isNotEmpty(collect)) {
                        String name = field.getName();
                        String head = name.substring(0, 1);
                        String method = "get" + name.replaceFirst(head, head.toUpperCase());
                        Result result = invoker.invoke(new BeanInvocation(method, null, null));
                        Object value = result.getValue();

                        if (collect.contains(NotNull.class) || collect.contains(NotEmpty.class)) {
                            if (null == value)
                                throw new BeanFieldException(String.format("%s is null", field.getName()), new NullPointerException());
                        }
                        if (collect.contains(NotEmpty.class)) {
                            if (value instanceof String && StringUtils.isEmpty(String.valueOf(value)))
                                throw new BeanFieldException(String.format("%s is empty", field.getName()));
                        }
                        if (result.getException() != null)
                            throw new BeanFieldException(result.getException().getMessage(), result.getException());
                        System.out.printf("此处可以判断是否为空:%s\n", String.valueOf(value));
                    }
                });
    }
}


/**
 * bean执行检查方法的类
 *
 * @author zhangjuntao
 * @create 2017-10-27 下午3:22
 **/
class BeanInvocation implements Invocation {

    private String methodName;

    private Class<?>[] parameterTypes;

    private Object[] arguments;

    public BeanInvocation() {
    }

    public BeanInvocation(String methodName, Class<?>[] parameterTypes, Object[] arguments) {
        this.methodName = methodName;
        this.parameterTypes = parameterTypes != null ? parameterTypes : new Class[0];
        this.arguments = arguments != null ? arguments : new Object[0];
    }

    @Override
    public String getMethodName() {
        return methodName;
    }

    @Override
    public Class<?>[] getParameterTypes() {
        return parameterTypes;
    }

    @Override
    public Object[] getArguments() {
        return arguments;
    }

    @Override
    public Map<String, String> getAttachments() {
        return null;
    }

    @Override
    public String getAttachment(String key) {
        return null;
    }

    @Override
    public String getAttachment(String key, String defaultValue) {
        return null;
    }

    @Override
    public Invoker<?> getInvoker() {
        return null;
    }
}

/**
 * bean检查异常类
 *
 * @author zhangjuntao
 * @create 2017-10-27 下午3:22
 **/
class BeanFieldException extends RuntimeException {
    public BeanFieldException(String message, Throwable cause) {
        super(message, cause);
    }

    public BeanFieldException(String message) {
        super(message);
    }
}